﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Common;

namespace DriverZwaveZensys
{
    using Zensys.ZWave;
    using Zensys.ZWave.Devices;
    using Zensys.ZWave.Enums;
    using Zensys.ZWave.Events;

    [System.AddIn.AddIn("DriverZwaveZensys")]
    public class DriverZwaveZensys : Common.ModuleBase
    {
        class MyCommandClass
        {
            public CommandClasses.CommandClass CmdClass { get; private set; }

            public MyCommandClass(CommandClasses.CommandClass cmdClass)
            {
                this.CmdClass = cmdClass;
            }

            public override bool Equals(object obj)
            {
                MyCommandClass otherCommandClass = obj as MyCommandClass;

                if (otherCommandClass == null)
                    return false;

                return (CmdClass.Key == otherCommandClass.CmdClass.Key);
            }

            public override int GetHashCode()
            {
                return CmdClass.Key.GetHashCode();
            }

            public override string ToString()
            {
                return CmdClass.Name;
            }
        }

        class ZwaveNode
        {
            public IDeviceInfo deviceInfo;
            public Port port = null;
            public Dictionary<MyCommandClass, List<ParameterValue>> values = new Dictionary<MyCommandClass,List<ParameterValue>>();

            public ZwaveNode(IDeviceInfo info, Port port)
            {
                deviceInfo = info;
                this.port = port;
            }


            internal bool ValueChanged(MyCommandClass myClass, List<ParameterValue> newValue)
            {
                List<ParameterValue> oldValue = values[myClass];
                
                if (oldValue == null) 
                    return true;

                if (oldValue.Count != newValue.Count)
                    return true;

                for (int index = 0; index < oldValue.Count; index++)
                {
                    ParameterValue oldPVal = oldValue[index];
                    ParameterValue newPVal = newValue[index];

                    if (oldPVal.Text != newPVal.Text)
                        return true;

                    if (!oldPVal.Value.SequenceEqual(newPVal.Value))
                        return true;
                }

                return false;
            }
        }

        private class OutboundRequest
        {
            public byte NodeId {get; private set;}
            public byte[] Data {get; private set;}
            public string Description {get; private set;}

            public OutboundRequest(byte nodeId, byte[] data, string desc)
            {
                this.NodeId = nodeId;
                this.Data = data;
                this.Description = desc;
            }
        }

        private IController controller;

        Queue<OutboundRequest> outboundRequests = new Queue<OutboundRequest>();

        private Dictionary<byte, ZwaveNode> zwaveNodes = new Dictionary<byte, ZwaveNode>();

        List<byte> excludedNodeIds = new List<byte>();

        public override void Start()
        {
            //AppDomain.CurrentDomain.AppendPrivatePath(Globals.AddInRoot + "\\AddIns\\DriverZwaveZensys\\");
            //logger.Log("1: {0}", AppDomain.CurrentDomain.SetupInformation.PrivateBinPath);
            //AppDomain.CurrentDomain.SetupInformation.PrivateBinPath = AppDomain.CurrentDomain.BaseDirectory;
            //logger.Log("2: {0}", AppDomain.CurrentDomain.SetupInformation.PrivateBinPath);
            //logger.Log("3: {0}", AppDomain.CurrentDomain.SetupInformation.PrivateBinPathProbe);

            //initialize the manager
            ZWaveManager zwaveManager = new ZWaveManager();
            zwaveManager.Init(LibraryModes.PcController, true, Globals.AddInRoot + "\\AddIns\\DriverZwaveZensys\\");
            controller = zwaveManager.ApplicationLayer.CreateController();

            bool foundContoller = false;

            //get the list of serial interfaces
            List<Zensys.Framework.Win32PnPEntityClass>
            interfaces = Zensys.Framework.ComputerSystemHardwareHelper.GetWin32PnPEntityClassSerialPortDevices();

            foreach (Zensys.Framework.Win32PnPEntityClass serialPortInfo in interfaces)
            {
                try
                {
                    controller.Open(serialPortInfo.DeviceID);
                    controller.GetVersion();
                    Libraries deviceLibrary = controller.Version.Library;

                    foundContoller = true;
                    logger.Log("found controller at {0} with lib {1}", serialPortInfo.Caption, deviceLibrary.ToString());

                    break;

                }
                catch (Exception)
                {
                    logger.Log("couldn't find controller at {0}", serialPortInfo.Caption);
                    controller.Close();
                }
            }

            if (foundContoller)
            {
                foreach (string nodeId in moduleInfo.Args())
                {
                    excludedNodeIds.Add(byte.Parse(nodeId));
                }

                InitController();

                #region //code for generating fake notifications -- should normally be commented out
                //while (true)
                //{
                //    foreach (ZwaveNode node in zwaveNodes.Values)
                //    {
                //        if (node.deviceInfo.Generic == GenericDevices.Instance.GENERIC_TYPE_SENSOR_BINARY.Key)
                //        {
                //            IList<View.VParamType> retVals = new List<View.VParamType>();
                //            retVals.Add(new ParamType(ParamType.SimpleType.integer, "8", (byte) 255, "value"));
                //            node.port.Notify(RoleSensor.RoleName, RoleSensor.OpGetName, retVals);
                //            logger.Log("Issued fake notification for " + node.port.ToString());
                //        }
                //    }
                //    System.Threading.Thread.Sleep(20 * 1000);
                //}
                #endregion

                System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);
            }
            else
            {
                logger.Log("No controller was found! Exiting.");
                return;
            }

        }

        private void InitController()
        {
            controller.ApplicationCommandHandlerEvent += new DeviceAppCommandHandlerEventHandler(ApplicationCommandHandlerEvent);
            controller.UnsolititedFrameReceived += new UnsolititedFrameReceivedEventHandler(UnsolititedFrameReceived);

            //controller.NodeStatusChanged += new NodeStatusChangedEventHandler(NodeStatusChanged);
            //controller.ApplicationSlaveCommandHandlerEvent += new DeviceAppSlaveCommandHandlerEventHandler(ApplicationSlaveCommandHandlerEvent);
            //controller.ControllerUpdated += new ControllerUpdatedEventHandler(ControllerUpdated);

            List<IDeviceInfo> devicesToInit = new List<IDeviceInfo>();

            List<Zensys.ZWave.Devices.IDeviceInfo> nodeList = controller.GetNodes();
            foreach (IDeviceInfo nodeInfo in nodeList)
            {

                if (nodeInfo.Generic == GenericDevices.Instance.GENERIC_TYPE_STATIC_CONTROLLER.Key)
                    continue;

                if (excludedNodeIds.Contains(nodeInfo.Id))
                    continue;

                IDeviceInfo deviceInfo = controller.RequestNodeInfo(nodeInfo.Id);

                if (deviceInfo != null)
                    devicesToInit.Add(deviceInfo);
                else
                    devicesToInit.Add(nodeInfo);
            }

            foreach (IDeviceInfo deviceInfo in devicesToInit)
            {
                InitDevice(deviceInfo);
            }
        }

        private void InitDevice(IDeviceInfo deviceInfo)
        {

            if (deviceInfo.Generic == GenericDevices.Instance.GENERIC_TYPE_SWITCH_MULTILEVEL.Key)
            {
                View.VPortInfo pInfo = GetPortInfoFromPlatform("Zwave Node::" + deviceInfo.Id);
                Port port = InitPort(pInfo);

                ZwaveNode node = new ZwaveNode(deviceInfo, port);
                zwaveNodes.Add(deviceInfo.Id, node);

                MyCommandClass myClass = new MyCommandClass(CommandClasses.Instance.COMMAND_CLASS_SWITCH_MULTILEVEL);
                node.values.Add(myClass, null);

                QueueRequest(new OutboundRequest(deviceInfo.Id, FrameGenerator.Zw_SwitchMultilevelGetFrame(), "SwithMultiLevelGet"));

                OperationDelegate handler = delegate(string roleName, string opName, IList<View.VParamType> list)
                {
                    return OnOperationInvoke(deviceInfo.Id, roleName, opName, list);
                };

                BindRoles(port, new List<View.VRole>() { new RoleSwitchMultiLevel() }, handler);

                RegisterPortWithPlatform(port);
            }
            else if (deviceInfo.Generic == GenericDevices.Instance.GENERIC_TYPE_SWITCH_BINARY.Key)
            {
                View.VPortInfo pInfo = GetPortInfoFromPlatform("Zwave Node::" + deviceInfo.Id);
                Port port = InitPort(pInfo);

                ZwaveNode node = new ZwaveNode(deviceInfo, port);
                zwaveNodes.Add(deviceInfo.Id, node);

                MyCommandClass myClass = new MyCommandClass(CommandClasses.Instance.COMMAND_CLASS_SWITCH_BINARY);
                node.values.Add(myClass, null);

                QueueRequest(new OutboundRequest(deviceInfo.Id, FrameGenerator.Zw_SwitchBinaryGetFrame(), "SwithBinaryGet"));

                OperationDelegate handler = delegate(string roleName, string opName, IList<View.VParamType> list)
                {
                    return OnOperationInvoke(deviceInfo.Id, roleName, opName, list);
                };

                BindRoles(port, new List<View.VRole>() { new RoleSwitchBinary() }, handler);

                RegisterPortWithPlatform(port);
            }
            else if (deviceInfo.Generic == GenericDevices.Instance.GENERIC_TYPE_SENSOR_BINARY.Key)
            {
                View.VPortInfo pInfo = GetPortInfoFromPlatform("Zwave Node::" + deviceInfo.Id);
                Port port = InitPort(pInfo);

                ZwaveNode node = new ZwaveNode(deviceInfo, port);
                zwaveNodes.Add(deviceInfo.Id, node);

                MyCommandClass myClass1 = new MyCommandClass(CommandClasses.Instance.COMMAND_CLASS_BASIC);
                node.values.Add(myClass1, null);
                MyCommandClass myClass2 = new MyCommandClass(CommandClasses.Instance.COMMAND_CLASS_SENSOR_MULTILEVEL);
                node.values.Add(myClass2, null);

                OperationDelegate handler = delegate(string roleName, string opName, IList<View.VParamType> list)
                {
                    return OnOperationInvoke(deviceInfo.Id, roleName, opName, list);
                };

                BindRoles(port, new List<View.VRole>() { new RoleSensor() }, handler);

                RegisterPortWithPlatform(port);

            }
#if FEATURE_METER
            else if (deviceInfo.Generic == GenericDevices.Instance.GENERIC_TYPE_METER.Key)
            {
                View.VPortInfo pInfo = GetPortInfoFromPlatform("Zwave Node::" + deviceInfo.Id);
                Port port = InitPort(pInfo);

                ZwaveNode node = new ZwaveNode(deviceInfo, port);
                zwaveNodes.Add(deviceInfo.Id, node);

                MyCommandClass myClass = new MyCommandClass(CommandClasses.Instance.COMMAND_CLASS_METER);
                node.values.Add(myClass, null);

                QueueRequest(new OutboundRequest(deviceInfo.Id, FrameGenerator.Zw_MeterGetFrame(), "MeterGet"));

                OperationDelegate handler = delegate(string roleName, string opName, IList<View.VParamType> list)
                {
                    return OnOperationInvoke(deviceInfo.Id, roleName, opName, list);
                };

                BindRoles(port, new List<View.VRole>() { new RoleMeter() }, handler);

                RegisterPortWithPlatform(port);

            }
#endif
            else
            {

                logger.Log("Not using zwave node {0} of type {1}", deviceInfo.Id.ToString(), GenericDevices.FindByKey(deviceInfo.Generic).Name);
            }

        }

        private void QueueRequest(OutboundRequest request)
        {
            lock (outboundRequests)
            {
                outboundRequests.Enqueue(request);

                if (outboundRequests.Count == 1)
                {
                    SafeThread newThread = new SafeThread(delegate() { SendRequests(); },
                                                              "SendRequests",
                                                              logger);
                    newThread.Start();
                }
            }
        }

        private void SendRequests()
        {
            OutboundRequest requestToSend = null;

            lock (outboundRequests)
            {
                if (outboundRequests.Count > 0) 
                    requestToSend = outboundRequests.Dequeue();
            }

            if (requestToSend != null)
            {
                SendRequest(requestToSend);
            }

            int remainingCount = 0;
            lock (outboundRequests)
            {
                remainingCount = outboundRequests.Count;
            }

            if (remainingCount > 0)
                  SendRequests();
        }

        private bool SendRequest(OutboundRequest request)
        {
            logger.Log("about to send request {0} to {1}", request.Description, request.NodeId.ToString());

            TransmitStatuses ret = controller.SendData(request.NodeId,
                                                       request.Data,
                                TransmitOptions.TransmitOptionAutoRoute |
                                TransmitOptions.TransmitOptionAcknowledge);


            logger.Log("status of request {0} to {1}: {2}", request.Description, request.NodeId.ToString(), ret.ToString());

            if (ret == TransmitStatuses.CompleteOk)
                return true;
            else
            {
                logger.Log("SendRequest failed {0}", ret.ToString());
                return false;
            }

            ////first try failed, try again
            //System.Threading.Thread.Sleep(1000);

            //TransmitStatuses ret2 = controller.SendData(request.NodeId,
            //                                       request.Data,
            //                TransmitOptions.TransmitOptionAutoRoute |
            //                TransmitOptions.TransmitOptionAcknowledge);

            //logger.Log("status of request {0} to {1} [2nd try]: {2}", request.Description, request.NodeId.ToString(), ret2.ToString());

            //return (ret2 == TransmitStatuses.CompleteOk);

            //return false;
        }

        IList<View.VParamType> OnOperationInvoke(byte nodeId, string roleName, string opName, IList<View.VParamType> list)
        {
            lock (this)
            {
                if (!zwaveNodes.ContainsKey(nodeId))
                    return null;

                ZwaveNode node = zwaveNodes[nodeId];

                //cobmined code for handling multi-level and binary switches
                if (node.deviceInfo.Generic == GenericDevices.Instance.GENERIC_TYPE_SWITCH_MULTILEVEL.Key ||
                    node.deviceInfo.Generic == GenericDevices.Instance.GENERIC_TYPE_SWITCH_BINARY.Key )
                {
                    if (!roleName.Equals(RoleSwitchMultiLevel.RoleName, StringComparison.CurrentCultureIgnoreCase) &&
                        !roleName.Equals(RoleSwitchBinary.RoleName, StringComparison.CurrentCultureIgnoreCase))
                        return null;

                    bool multiLevel = (node.deviceInfo.Generic == GenericDevices.Instance.GENERIC_TYPE_SWITCH_MULTILEVEL.Key);

                    MyCommandClass myClass = (multiLevel) ?
                        new MyCommandClass(CommandClasses.Instance.COMMAND_CLASS_SWITCH_MULTILEVEL) :
                        new MyCommandClass(CommandClasses.Instance.COMMAND_CLASS_SWITCH_BINARY);

                    switch (opName.ToLower())
                    {
                        case RoleSwitchMultiLevel.OpSetName:
                        // case RoleSwitchBinary.OpSetName -- this is same as the one above
                            {

                                byte valToSet = (byte)list[0].Value();
                                bool valChangeRequested = false;

                                List<ParameterValue> currentValList = node.values[myClass];

                                if (currentValList == null)
                                {
                                    currentValList = new List<ParameterValue>();

                                    ParameterValue pVal = new ParameterValue();
                                    pVal.Text = "Value";
                                    pVal.Value = new byte[] { valToSet };

                                    currentValList.Add(pVal);
                                    node.values[myClass] = currentValList;

                                    valChangeRequested = true;

                                }
                                else
                                {
                                    byte currentVal = currentValList[0].Value[0];

                                    if (currentVal != valToSet)
                                    {
                                        currentValList[0].Value[0] = valToSet;
                                        valChangeRequested = true;
                                    }
                                }

                                if (valChangeRequested)
                                {
                                    logger.Log("queueing switch level set for {0}", valToSet.ToString());

                                    if (multiLevel) 
                                        QueueRequest(new OutboundRequest(nodeId,
                                                                    FrameGenerator.Zw_SwitchMultilevelSetFrame(valToSet),
                                                                    "SwitchMultiLevelSet-" + valToSet));
                                    else
                                        QueueRequest(new OutboundRequest(nodeId,
                                                                        FrameGenerator.Zw_SwitchBinarySetFrame(valToSet),
                                                                        "SwitchBinarySet-" + valToSet));

                                    SafeThread newThread1 = new SafeThread(delegate() { Notify(node, myClass, currentValList); },
                                                                            "Notify-" + nodeId + "-" + myClass.CmdClass,
                                                                            logger);
                                    newThread1.Start();

                                    //IssueSwitchLevelSetQuery(nodeId, valToSet);
                                    //Notify(node, myClass, currentValList);
                                }

                                return null;
                            }

                        case RoleSwitchMultiLevel.OpGetName:
                        ///case RoleSwitchBinary.OpGetName: -- this is same as the one above
                            {
                                byte value = 0;

                                List<ParameterValue> currentValList = node.values[myClass];
                                if (currentValList != null)
                                    value = currentValList[0].Value[0];

                                logger.Log("DriverZwaveZensys: OpGetName returning {0}",  value.ToString());

                                IList<View.VParamType> retVals = new List<View.VParamType>();
                                retVals.Add(new ParamType(ParamType.SimpleType.integer, "8", value, "level"));

                                return retVals;
                            }
                        default:
                            logger.Log("Unknown operation {0} for role {1}", opName, roleName);
                            return null;
                    }
                }
                else if (node.deviceInfo.Generic == GenericDevices.Instance.GENERIC_TYPE_SENSOR_BINARY.Key)
                {
                    if (!roleName.Equals(RoleSensor.RoleName, StringComparison.CurrentCultureIgnoreCase))
                        return null;

                    MyCommandClass myClass = new MyCommandClass(CommandClasses.Instance.COMMAND_CLASS_BASIC);

                    switch (opName.ToLower())
                    {
                        case RoleSensor.OpGetName:
                            {
                                //lets default to zero if we don't know the value yet
                                byte value = 0;

                                List<ParameterValue> currentValList = node.values[myClass];
                                if (currentValList != null)
                                    value = currentValList[0].Value[0]; //extracting value

                                logger.Log("DriverZwaveZensys: OpGetName returning {0}", value.ToString());

                                IList<View.VParamType> retVals = new List<View.VParamType>();
                                retVals.Add(new ParamType(ParamType.SimpleType.integer, "8", value, "level"));

                                return retVals;
                            }
                        default:
                            logger.Log("Unknown operation {0} for role {1}", opName, roleName);
                            return null;
                    }
                }
#if FEATURE_METER
                else if (node.deviceInfo.Generic == GenericDevices.Instance.GENERIC_TYPE_METER.Key)
                {
                    if (!roleName.Equals(RoleMeter.RoleName, StringComparison.CurrentCultureIgnoreCase))
                        return null;

                    MyCommandClass myClass = new MyCommandClass(CommandClasses.Instance.COMMAND_CLASS_BASIC);

                    switch (opName.ToLower())
                    {
                        case RoleMeter.OpGetMeter:
                            {
                                //lets default to zero if we don't know the value yet
                                byte value = 0;

                                List<ParameterValue> currentValList = node.values[myClass];
                                if (currentValList != null)
                                    value = currentValList[0].Value[0]; //extracting value

                                logger.Log("DriverZwaveZensys: OpGetName returning {0}", value.ToString());

                                IList<View.VParamType> retVals = new List<View.VParamType>();
                                retVals.Add(new ParamType(ParamType.SimpleType.integer, "8", value, "level"));

                                return retVals;
                            }
                        default:
                            logger.Log("Unknown operation {0} for role {1}", opName, roleName);
                            return null;
                    }
                }
#endif
                else
                {
                    throw new NotImplementedException("what?!");
                }
            }
        }

        private void ApplicationCommandHandlerEvent(DeviceAppCommandHandlerEventArgs args)
        {
            lock (this)
            {
                //temp logger.Log("zwave application command handler called");

                if (args.SourceNodeId == 0 || !zwaveNodes.ContainsKey(args.SourceNodeId))
                    return;

                ZwaveNode node = zwaveNodes[args.SourceNodeId];

                CommandClasses.CommandClass commandClass = CommandClasses.FindByName((CommandClasses.Types)args.CommandClassKey);
                if (commandClass == null)
                    return;

                MyCommandClass myClass = new MyCommandClass(commandClass);
                if (!node.values.ContainsKey(myClass))
                    return;

                CommandClasses.Command command = commandClass.FindCommandByKey(args.CommandKey);
                if (command == null)
                    return;

                List<ParameterValue> newValue = GetValue(commandClass, command, args);

                if (node.ValueChanged(myClass, newValue))
                {
                    node.values[myClass] = newValue;
                    Notify(node, myClass, newValue);
                }
            }

        }

        private void Notify(ZwaveNode node, MyCommandClass myClass, List<ParameterValue> newValue)
        {
            lock (this)
            {
                if (node.deviceInfo.Generic == GenericDevices.Instance.GENERIC_TYPE_SWITCH_MULTILEVEL.Key)
                {
                    if (myClass.CmdClass.Key == CommandClasses.Instance.COMMAND_CLASS_SWITCH_MULTILEVEL.Key)
                    {
                        //this is how we extract the value for switch multi level
                        byte value = newValue[0].Value[0];

                        IList<View.VParamType> retVals = new List<View.VParamType>();
                        retVals.Add(new ParamType(ParamType.SimpleType.integer, "8", value, "level"));

                        node.port.Notify(RoleSwitchMultiLevel.RoleName, RoleSwitchMultiLevel.OpGetName, retVals);

                        logger.Log("{0}: issued notification for switch {1}, value {2}", ToString(), node.deviceInfo.Id.ToString(), value.ToString());
                    }
                    else
                    {
                        logger.Log("{0}: got unsupported command class {1} for switchmultilevel", ToString(), myClass.CmdClass.Name);
                    }
                }
                else if (node.deviceInfo.Generic == GenericDevices.Instance.GENERIC_TYPE_SWITCH_BINARY.Key)
                {
                    if (myClass.CmdClass.Key == CommandClasses.Instance.COMMAND_CLASS_SWITCH_BINARY.Key)
                    {
                        //this is how we extract the value for switch multi level
                        byte value = newValue[0].Value[0];

                        IList<View.VParamType> retVals = new List<View.VParamType>();
                        retVals.Add(new ParamType(ParamType.SimpleType.integer, "8", value, "level"));

                        node.port.Notify(RoleSwitchBinary.RoleName, RoleSwitchBinary.OpGetName, retVals);

                        logger.Log("{0}: issued notification for binary switch {1}, value {2}", ToString(), node.deviceInfo.Id.ToString(), value.ToString());
                    }
                    else
                    {
                        logger.Log("{0}: got unsupported command class {1} for switchbinary", ToString(), myClass.CmdClass.Name);
                    }
                }
                else if (node.deviceInfo.Generic == GenericDevices.Instance.GENERIC_TYPE_SENSOR_BINARY.Key)
                {
                    if (myClass.CmdClass.Key == CommandClasses.Instance.COMMAND_CLASS_BASIC.Key)
                    {
                        //this is how we extract the value for sensor binary
                        byte value = newValue[0].Value[0];

                        IList<View.VParamType> retVals = new List<View.VParamType>();
                        retVals.Add(new ParamType(ParamType.SimpleType.integer, "8", node.deviceInfo.Id, "basic sensor type"));
                        retVals.Add(new ParamType(ParamType.SimpleType.integer, "8", value, "value"));

                        node.port.Notify(RoleSensor.RoleName, RoleSensor.OpGetName, retVals);
                        
                        logger.Log("{0}: event notification from door/motion sensor [id={1}, Status=0x{2:X}]", ToString(), node.deviceInfo.Id.ToString(), String.Format("{0:X}", value));
                    }
                    else if (myClass.CmdClass.Key == CommandClasses.Instance.COMMAND_CLASS_SENSOR_MULTILEVEL.Key)
                    {
                        //this is how we extract the value for sensor binary
                        byte type = newValue[0].Value[0];
                        short value = 0;

                        if (type == 1 || type == 3)
                            value = (short)(newValue[2].Value[0] * 0x100 + newValue[2].Value[1]);
                        else if (type == 5)
                            value = (byte)newValue[2].Value[0];

                        IList<View.VParamType> retVals = new List<View.VParamType>();
                        retVals.Add(new ParamType(ParamType.SimpleType.integer, "8", type, "value1"));
                        retVals.Add(new ParamType(ParamType.SimpleType.integer, "16", value, "value3"));

                        node.port.Notify(RoleSensor.RoleName, RoleSensor.OpGetMulti, retVals);

                        logger.Log("{0}: event notification from multi sensor [id={1}, type={2} value=0x{3:X}]", ToString(), node.deviceInfo.Id.ToString(), type.ToString(), String.Format("{0:X}", value));
                    }
                    else
                    {
                        logger.Log("{0}: got unsupported command class {1} for sensor", ToString(), myClass.CmdClass.Name);
                    }
                }
#if FEATURE_METER
                else if (node.deviceInfo.Generic == GenericDevices.Instance.GENERIC_TYPE_METER.Key)
                {
                    if (myClass.CmdClass.Key == CommandClasses.Instance.COMMAND_CLASS_BASIC.Key)
                    {
                        //this is how we extract the value for sensor binary
                        byte value = newValue[0].Value[0];

                        IList<View.VParamType> retVals = new List<View.VParamType>();
                        retVals.Add(new ParamType(ParamType.SimpleType.integer, "8", value, "value"));

                        node.port.Notify(RoleMeter.RoleName, RoleMeter.OpGetMeter, retVals);

                        logger.Log("{0}: issued notification for Meter {1}, value {2}", ToString(), node.deviceInfo.Id.ToString(), value.ToString());
                    }
                    else
                    {
                        logger.Log("{0}: got unsupported command Meter {1} for sensor", ToString(), myClass.CmdClass.Name);
                    }
                }
#endif
                else
                {
                    logger.Log("{0}: notify hasn't been implemented for {1}", ToString(), GenericDevices.FindByKey(node.deviceInfo.Generic).Name);
                }
            }
        }

        private List<ParameterValue> GetValue(CommandClasses.CommandClass cmdClass, CommandClasses.Command command, DeviceAppCommandHandlerEventArgs args) {

            StringBuilder sb = new StringBuilder();
            sb.Append(string.Format("{0} received", command.Name));

            try
            {
                if (args.ParameterValues != null &&
                    args.ParameterValues is List<ParameterValue> &&
                    ((List<ParameterValue>)args.ParameterValues).Count > 0)
                {
                    sb.Append(", parameters: ");
                    foreach (ParameterValue pval in ((List<ParameterValue>)args.ParameterValues))
                    {
                        sb.Append(string.Format("{0}={1}; ", pval.Text, Zensys.Framework.Tools.ToHexString(pval.Value, " ")));
                    }

                    logger.Log(sb.ToString());

                    return (List<ParameterValue>)args.ParameterValues;
                }
                else
                {
                    logger.Log("Couldn't extract value: ", sb.ToString());
                }

            }
            catch (Exception e)
            {
                logger.Log("getvalue interrupted at {0}\n{1}", sb.ToString(), e.ToString());
            }

            return null;
        }

        private void UnsolititedFrameReceived(UnsolititedFrameReceivedArgs args)
        {
            lock (this)
            {
                StringBuilder sb = new StringBuilder();
                foreach (byte val in args.Data)
                {
                    sb.Append(val + " ");
                }
                //temp logger.Log("zwave unsolicited frame received: {0}", sb.ToString());

                if (args.Data[3] == (byte)CommandTypes.CmdApplicationControllerUpdate &&
                    args.Data[4] == (byte)ApplicationControllerUpdateStatuses.NODE_INFO_RECEIVED &&
                    zwaveNodes.ContainsKey(args.Data[5]))
                {

                    ZwaveNode node = zwaveNodes[args.Data[5]];

                    if (node.deviceInfo.Generic == GenericDevices.Instance.GENERIC_TYPE_SWITCH_MULTILEVEL.Key)
                    {
                        QueueRequest(new OutboundRequest(args.Data[5], FrameGenerator.Zw_SwitchMultilevelGetFrame(), "SwithMultiLevelGet"));
                    }
                    else if (node.deviceInfo.Generic == GenericDevices.Instance.GENERIC_TYPE_SWITCH_BINARY.Key)
                    {
                        QueueRequest(new OutboundRequest(args.Data[5], FrameGenerator.Zw_SwitchBinaryGetFrame(), "SwithBinaryGet"));
                    }
                    else if (node.deviceInfo.Generic == GenericDevices.Instance.GENERIC_TYPE_SENSOR_BINARY.Key)
                    {
                        QueueRequest(new OutboundRequest(args.Data[5], FrameGenerator.Zw_SensorBinaryGetFrame(), "SensorBinaryGet"));
                    }
#if FEATURE_METER
                    else if (node.deviceInfo.Generic == GenericDevices.Instance.GENERIC_TYPE_METER.Key)
                    {
                        QueueRequest(new OutboundRequest(args.Data[5], FrameGenerator.Zw_MeterGetFrame(), "MeterGet"));
                    }
#endif
                }
            }
        }

        public override void Stop()
        {
            throw new NotImplementedException();
        }

        //we have nothing to do with other ports
        public override void PortRegistered(View.VPort port) {}
        public override void PortDeregistered(View.VPort port) {}

    }
}
